#include <QtNetwork>
#include "network.h"
#include "connection.h"
#include "peermanager.h"
#include "fileserver.h"
#include "fileclient.h"
#include <QFileInfo>
#include "api.h"
Network::Network(QObject *parent) : QObject(parent)
{

}

void Network::sendMessage(QObject *connection, const QVariantMap &message)
{
    QJsonDocument document(QJsonObject::fromVariantMap(message));

    Connection * con =  qobject_cast<Connection *>(connection);

    if (!con)
        return;

    con->sendMessage(document.toJson());
}

void Network::sendFile(const QString &fileName, const QString &identity)
{
    QString token = "";
    QList<Connection *> connections = peers.values();
    foreach (Connection *connection, connections){
        qDebug() << "<<<<<<<<<<<<<<<<<<<<<";
        qDebug() << "nick:" << connection->identity();

        if (connection->identity() == identity){
            QJsonObject object;
            FileServer * server = new FileServer(this);

            server->setFileName(fileName);

            QFileInfo fileInfo(fileName);

            object.insert("fileName", fileInfo.fileName());
            qint64 size = fileInfo.size();
            object.insert("size", size);
            server->setSize(size);

            {
                QFile file(fileName);
                file.open(QIODevice::ReadOnly);
                object.insert("md5", QString(QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5).toHex()));
            }
            server->listen();
            token = server->token();
            connect(server, &FileServer::closed, [token, this](){
                //emit this->fileClosed(token);
            });

            connect(server, SIGNAL(closed()), server, SLOT(deleteLater()));
            connect(server, &FileServer::progress, this, [this](qint64 received, qint64 total){
                //this->progress(this->m_identity,received,total);
            });
            m_fileServer << server;

            object.insert("url", "ws://" + Api::activeHostAddress(/*QHostAddress::AnyIPv4*/).toString() + ":" + QString::number(server->port()));

            qDebug() << "active url: " << Api::activeHostAddress(/*QHostAddress::AnyIPv4*/).toString();
            QJsonDocument document(object);
            connection->sendFile(document.toJson());
            break;
        }
    }
}

void Network::sendFile(const QString &fileName)
{
    QString token = "";
    QList<Connection *> connections = peers.values();

    QJsonObject object;
    FileServer * server = new FileServer(this);

    server->setFileName(fileName);

    QFileInfo fileInfo(fileName);

    object.insert("fileName", fileInfo.fileName());
    qint64 size = fileInfo.size();
    object.insert("size", size);
    server->setSize(size);

    {
        QFile file(fileName);
        file.open(QIODevice::ReadOnly);
        object.insert("md5", QString(QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5).toHex()));
    }
    server->listen();
    token = server->token();
    connect(server, &FileServer::closed, [token, this](){
        //emit this->fileClosed(token);
    });

    connect(server, SIGNAL(closed()), server, SLOT(deleteLater()));
    connect(server, &FileServer::progress, this, [this](qint64 received, qint64 total){
        //this->progress(this->m_identity,received,total);
    });
    m_fileServer << server;

    object.insert("url", "ws://" + Api::activeHostAddress(/*QHostAddress::AnyIPv4*/).toString() + ":" + QString::number(server->port()));

    qDebug() << "active url: " << Api::activeHostAddress(/*QHostAddress::AnyIPv4*/).toString();
    QJsonDocument document(object);

    foreach (Connection *connection, connections){
        qDebug() << "<<<<<<<<<<<<<<<<<<<<<";
        qDebug() << "nick:" << connection->identity();

        connection->sendFile(document.toJson());

    }
}

void Network::receiveFile(const QVariantMap &message)
{
    QString  url = message.value("url").toString();
    QString fileName = message.value("fileName").toString();
    QString fileDir = message.value("filePath").toString();
    qint64 length = message.value("size").toLongLong();
    QString md5 = message.value("md5").toString();
    // FILENAME filename + index + fx

    QString origineFilePath =QDir::toNativeSeparators(fileDir + QDir::separator() + fileName);

    qint64 size = 0;
   QFileInfo fileInfo(origineFilePath);

    if (QFile::exists(origineFilePath))
    {
        QString omd5 = Api::fileMD5(origineFilePath);
        size = fileInfo.size();
        if (omd5 == md5) {
            QString token = QUuid::createUuid().toString();
            //emit progress(m_identity, size, size);
            return;
        }
        else
        {
            origineFilePath = fileInfo.absoluteFilePath()
                    + QString::number(qChecksum(QUuid::createUuid().toByteArray(), 36))
                    + "." + fileInfo.suffix();
        }
    }

    QString filePath = origineFilePath + ".fx";

    if (QFile::exists(filePath))
    {
        QFileInfo fxfileInfo(filePath);

        QString fxmd5 = Api::fileMD5(filePath);

        if (md5 == fxmd5)
        {
            QString newFileName = filePath;
            newFileName.chop(fxfileInfo.suffix().length() + 1);
             QFile::rename(filePath, newFileName);
            QString token = QUuid::createUuid().toString();
            //emit progress(m_identity, size, size);

            return;
        }
        else
        {
            size = fxfileInfo.size();
        }
    }

    FileClient * client = new FileClient(this);
    client->setFilePos(size);
    client->setSize(size);
    client->setLength(length);
    client->setFileName(filePath);
    client->setMD5(md5);
    QString token = client->token();
    connect(client, &FileClient::closed, [token, this](){
        //emit this->fileClosed(token);
    });

    connect(client, &FileClient::progress, this, [this](qint64 received, qint64 total){
        //this->progress(this->m_identity,received,total);
    });

    connect(client, SIGNAL(closed()), client, SLOT(deleteLater()));

    qDebug() << "connected url:" << url;
    client->open(url);

    m_fileClient << client;
}

void Network::cancelFileServerTransport(const QString &token)
{
    foreach (FileServer *s, m_fileServer) {
            if (s->token() == token) {
                s->deleteLater();
            }
    }
}

void Network::cancelFileClientTransport(const QString &token)
{
    foreach (FileClient *c, m_fileClient) {
            if (c->token() == token) {
                c->deleteLater();
            }
    }
}


bool Network::hasConnection(const QHostAddress &address, int port) const
{
    if (!peers.contains(address))
        return false;

    QList<Connection *> connections = peers.values(address);
    foreach (Connection *connection, connections) {
        if (connection->peerPort() == port)
            return true;
    }

    return false;
}

bool Network::listen(const QString &identity)
{
    m_identity = identity;

    if (m_identity.isEmpty())
        return false;

    qInfo() << "#listen " << m_identity << ":" << server.serverPort();

    peerManager = new PeerManager(this);
    peerManager->setServerPort(server.serverPort());
    peerManager->startBroadcasting();

    connect(peerManager, SIGNAL(newConnection(const QHostAddress &, int)), this, SLOT(newConnection(const QHostAddress &, int)));
    connect(&server, SIGNAL(newConnection(Connection*)), this, SLOT(newConnection(Connection*)));
    connect(peerManager, SIGNAL(shopDetection(QUrl)), this, SIGNAL(shopDetection(QUrl)));
    return true;
}

void Network::newConnection(Connection *connection)
{
    qInfo() << "#info incoming ip:" << connection->peerAddress() << ":" << connection->peerPort();
    connection->setIdentity(m_identity);

    connect(connection, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError(QAbstractSocket::SocketError)));
    connect(connection, SIGNAL(disconnected()), this, SLOT(disconnected()));

    bool ok = false;
    connection->peerAddress().toIPv4Address(&ok);
    if (!ok) {
        connection->close();
    }

    connect(connection, SIGNAL(readyForUse()), this, SLOT(readyForUse()));
}

void Network::newConnection(const QHostAddress &address, int port)
{
    int count = peerTemps.count(address, port);
    peerTemps.insert(address, port);

    if(count > 3)
    {
        peerTemps.remove(address, port);
    }
    else if (count != 0) {
        return;
    }

    if (!this->hasConnection(address, port)) {
        Connection *connection = new Connection(this);

        connection->setIdentity(m_identity);

        connect(connection, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError(QAbstractSocket::SocketError)));
        connect(connection, SIGNAL(disconnected()), this, SLOT(disconnected()));
        connect(connection, SIGNAL(readyForUse()), this, SLOT(readyForUse()));

        connection->connectToHost(address, port);

        qInfo() << "#connect to " << address << ":" << port;
    }
}

void Network::readyForUse()
{
    Connection *connection = qobject_cast<Connection *>(sender());

    connect(connection, &Connection::newMessage, [this, connection](const QByteArray &message){
        qInfo() << "#rec: " << message;
        this->newMessage(connection, QJsonDocument::fromJson(message).object().toVariantMap());
    });

    connect(connection, &Connection::newFile, [this, connection](const QVariantMap &message){
        this->newFile(connection, message);
    });

    if (connection->identity().isEmpty()) {
        removeConnection(connection);
        return;
    }

    peers.insert(connection->peerAddress(), connection);

    qInfo() << "#new participant: " << connection->peerAddress() << ":" << connection->peerPort();

    emit newParticipant(connection);
}

void Network::disconnected()
{
    if (Connection *connection = qobject_cast<Connection *>(sender()))
        removeConnection(connection);
}

void Network::connectionError(QAbstractSocket::SocketError socketError)
{
    qWarning() << "#connection error: " << socketError;

    if (Connection *connection = qobject_cast<Connection *>(sender()))
        removeConnection(connection);
}

void Network::removeConnection(Connection *connection)
{
    qInfo() << "#remove connection: " << connection->peerAddress() << ":" << connection->peerPort();

    if (peers.contains(connection->peerAddress())) {
        peers.remove(connection->peerAddress());
        emit participantLeave(connection);
    }
    connection->deleteLater();
}
